#ifndef __VANILA_SCANNER_HH__
#define __VANILA_SCANNER_HH__

#include "vanila/token.h"
#include "utils/singleton.h"
#include <cstring>
#include <cstdint>

namespace vanila
{
class Scanner : public utils::Singleton<Scanner>
{
public:
    //! \brief Construct a new Scanner object
    Scanner() noexcept : _start{nullptr}, _current{nullptr}, _line{0}
    {}

    ~Scanner() noexcept = default;

    //! \brief init the Scanner!
    void init(const char* source) noexcept;

    //! \brief scan a new token in after current pointer
    Token scanToken() noexcept;

private:
    //! \brief check wheather reach the end of source code
    bool isAtEnd() const noexcept
    { return *(this->_current) == '\0'; }

    //! \brief check wheather current character is expected, is so current increase
    bool match(char expected) noexcept;

    //! \brief get the current character, and the current pointer increase
    char advance() noexcept
    { return *((this->_current)++); }

    //! \brief get the current character
    char peek() const noexcept 
    { return *(this->_current); }

    //! \brief get the next character
    char peekNext() const noexcept
    { return (this->isAtEnd()) ? ('\0') : (this->_current[1]); }

    //! \brief skip the white character
    void skipWhitecharacter() noexcept;

private:
    //! \brief create a specify type token with current scanner's message
    //! \param[in] tokenType the token type
    Token makeToken(TokenType tokenType) const noexcept
    { return Token(tokenType, this->_start, this->_current - this->_start, this->_line); }

    //! \brief create a ERROR type Token
    //! \param[in] message the message data in Token
    Token errorToken(const char* message) const noexcept
    { return Token(TokenType::ERROR, message, strlen(message), this->_line); }

private:
    //! \brief scan a number token after current character
    Token scanNumber() noexcept;

    //! \brief scan a string token after current character
    Token scanString() noexcept;

    //! \brief scan a identifier after current character
    Token scanIdentifier() noexcept;

    //! \brief get the identifier type of the string os start to current
    TokenType getIdentifierType() noexcept;

    //! \brief check the after start position of _start, is meet the string: rest,
    //! \brief is so, return type or IDENTIFIER
    TokenType checkKeyword(uint8_t start, uint8_t length, const char* rest, TokenType type) noexcept;

private:
    const char* _start;
    const char* _current;
    size_t _line;
};

}

#endif